package edu.pg.ipse.utils;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import org.apache.commons.math.MathException;
import org.apache.commons.math.distribution.ContinuousDistribution;
import org.apache.commons.math.special.Erf;
import org.apache.log4j.Logger;

import edu.pg.ipse.utils.io.ReadWithScanner;

/**
 * The class <code>MathUtils</code> contains methods for performing different type of 
 * numeric operations.
 */
public class MathUtils {
	
	private static Logger logger = Logger.getRootLogger(); 
	
	private static Map<Double, Double> laplasFunctionValues = new HashMap<Double, Double>();
	private static Map<Double, Double> laplasFunctionArgs = new HashMap<Double, Double>();
	
	static {
		init();
	}
	
	/**
	 * Logarithm operation.
	 * 
	 * @param number input number
	 * @param base logarithm base
	 * @return result
	 */
	public static Double log(double number, int base) {
		return Math.log10(number) / Math.log10(base);
	}
	
    /**
     * Returns the error function erf(x).
     *
     * The implementation of this method is based on {@link Erf#erf(double)}
     *
     * @param x the value.
     * @return the error function erf(x)
     * @throws MathException if the algorithm fails to converge.
     */
    public static double erf(double x) throws MathException {
    	return Erf.erf(x);
    }
	
	/**
	 * Calculates an approximate value of definite integral 
	 * 
	 * @param values
	 * @param startIndex
	 * @param endIndex
	 * @return
	 */
	public static double integral(double[] values, int startIndex, int endIndex) {
		double result = 0.0;
		for (int i = startIndex; i <= endIndex; i++) {
			result += values[i] * (values[i+1] - values[i]);
		}
		return result;
	}
	
	/**
	 * Calculates an approximate value of definite integral 
	 * 
	 * @param values
	 * @param startIndex
	 * @param endIndex
	 * @return
	 */
	public static double integral(ContinuousDistribution cd, 
			double startP, int endP, double step) throws MathException {
		double result = 0.0;
		for (double p = startP; p <= endP; p += step) {
			result += step * cd.inverseCumulativeProbability(p);
		}
		return result;
	}

	/**
     * Not tested yet
     */
	public static double lagrange_polynomial(double x[], double y[], double argx) {
		
		int n = x.length;
		double c;
		double s = 0;

		for (int i = 0; i < n; i++) {
			c = 1;
			for (int j = 0; j < n; j++) {
				if (i != j) {
					c *= (argx - x[j]) / (x[i] - x[j]);
				}
			}
			s += c * y[i];
		}
		return s;
	}
	
	/**
	 * Based on normal distribution function: 
	 * D(x) = 1/2 * [1 + erf((x - m)/(sigma * Math.sqrt(2))], 
	 * where "erf" (from "error function") is a laplas function
	 * 
	 * @param percentile
	 * @param m is a mean (location of the peak)
	 * @param sigma 
	 * 		is a standard deviation is (squere root of the 
	 * 		the variance [the measure of the width of the distribution])
	 * @return
	 * 
	 * @deprecated
	 */
	public static double getNormalPercentileValue(double percentile, double m, double sigma) {
		
		double laplasFunctionResult = 2 * percentile - 1;
		Double laplasArgument;
		
		boolean resultIsNegative = laplasFunctionResult < 0 ? true : false;
		if(resultIsNegative) {
			laplasFunctionResult *= -1;
		}
		laplasArgument = getLaplasFunctionArgument(laplasFunctionResult);
		if(laplasArgument == null) {
			return 0.0;
		}
		return laplasArgument.doubleValue() * sigma + m;
	}
	
	private static Double getLaplasFunctionArgument(Double value) {
		Set<Double> keySet = laplasFunctionArgs.keySet();
		for (Double key : keySet) {
			if(Math.abs(value - key) < 0.01) {
				return laplasFunctionArgs.get(key);
			}
		}
		throw new IllegalArgumentException("Bad laplas function result: " + value);
	}
	
	/**
	 * Initialize function.
	 */
	private static void init() {
		
		ReadWithScanner propertiesReader = new ReadWithScanner(".\\data\\laplasfunction.txt") {
			@Override
			protected void processLine(String line) {
				if(line == null) {
					return;
				}
				String[] split = line.split(" ");
				Double x = Double.parseDouble(split[0]);
				Double value = Double.parseDouble(split[1]);
				
				laplasFunctionValues.put(x, value);
				laplasFunctionArgs.put(value, x);
			}
		};
		
		try {
			propertiesReader.processLineByLine();
		} catch (Exception e) {
			logger.error(e.getMessage());
		}
	}
}
